use crate::error::AppError;
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
pub struct DvcFile {
    pub md5: String,
    pub cmd: String,
    pub deps: Vec<String>,
    pub outs: Vec<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct DvcOutput {
    // according to https://dvc.org/doc/user-guide/project-structure/dvc-files
    pub md5: String,
    pub path: String,
    #[serde(default = "default_cache")]
    pub cache: bool,
}

fn default_cache() -> bool {
    true
}

#[derive(Debug, Serialize, Deserialize)]
pub struct DvcMetadata {
    pub outs: Vec<DvcOutput>,
    pub deps: Option<Vec<DvcDependency>>,
    pub cmd: Option<String>,
    pub md5: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct DvcDependency {
    pub path: String,
    pub md5: Option<String>,
    pub checksum: Option<String>,
}

pub fn generate_dvc_file(file_path: &str, file_hash: &str) -> Result<String, AppError> {
    let output = DvcOutput {
        md5: file_hash.to_string(),
        path: file_path.to_string(),
        cache: true, // Explicitly set to true for generated files
    };

    let metadata = DvcMetadata {
        outs: vec![output],
        deps: None,
        cmd: None,
        md5: Some(file_hash.to_string()),
    };

    let yaml_content = serde_yaml::to_string(&metadata)
        .map_err(|e| AppError::Internal(format!("Failed to serialize DVC file: {}", e)))?;

    Ok(yaml_content)
}

pub fn parse_dvc_file(content: &str) -> Result<DvcMetadata, AppError> {
    let metadata: DvcMetadata = serde_yaml::from_str(content)
        .map_err(|e| AppError::Internal(format!("Failed to parse DVC file: {}", e)))?;

    Ok(metadata)
}

#[allow(dead_code)]
pub fn validate_dvc_file(content: &str, expected_file_path: &str) -> Result<bool, AppError> {
    let metadata = parse_dvc_file(content)?;

    // Check if the file path matches
    if let Some(output) = metadata.outs.first() {
        Ok(output.path == expected_file_path)
    } else {
        Ok(false)
    }
}

// NEW: Extract dependency paths from DVC file content
pub fn extract_dependencies_from_dvc(content: &str) -> Result<Vec<String>, AppError> {
    let metadata = parse_dvc_file(content)?;

    match metadata.deps {
        Some(deps) => {
            let paths: Vec<String> = deps.iter().map(|dep| dep.path.clone()).collect();
            Ok(paths)
        }
        None => Ok(Vec::new()),
    }
}

// NEW: Extract output paths from DVC file content
pub fn extract_outputs_from_dvc(content: &str) -> Result<Vec<String>, AppError> {
    let metadata = parse_dvc_file(content)?;

    let paths: Vec<String> = metadata.outs.iter().map(|out| out.path.clone()).collect();
    Ok(paths)
}

// NEW: Check if DVC file has dependencies
#[allow(dead_code)]
pub fn has_dependencies(content: &str) -> Result<bool, AppError> {
    let metadata = parse_dvc_file(content)?;
    Ok(metadata.deps.is_some() && metadata.deps.as_ref().unwrap_or(&vec![]).len() > 0)
}

// NEW: Get command from DVC file if present
#[allow(dead_code)]
pub fn get_command_from_dvc(content: &str) -> Result<Option<String>, AppError> {
    let metadata = parse_dvc_file(content)?;
    Ok(metadata.cmd)
}

// NEW: Enhanced DVC file parsing for pipeline files (dvc.yaml)
#[derive(Debug, Serialize, Deserialize)]
pub struct DvcPipeline {
    pub stages: std::collections::HashMap<String, DvcStage>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct DvcStage {
    pub cmd: String,
    pub deps: Option<Vec<String>>,
    pub outs: Option<Vec<String>>,
    pub params: Option<Vec<String>>,
    pub metrics: Option<Vec<String>>,
    pub plots: Option<Vec<String>>,
}

// NEW: Parse DVC pipeline file (dvc.yaml)
#[allow(dead_code)]
pub fn parse_dvc_pipeline(content: &str) -> Result<DvcPipeline, AppError> {
    let pipeline: DvcPipeline = serde_yaml::from_str(content)
        .map_err(|e| AppError::Internal(format!("Failed to parse DVC pipeline: {}", e)))?;

    Ok(pipeline)
}

// NEW: Extract all dependencies and outputs from DVC pipeline
#[allow(dead_code)]
pub fn extract_lineage_from_pipeline(
    content: &str,
) -> Result<Vec<(String, Vec<String>, Vec<String>)>, AppError> {
    let pipeline = parse_dvc_pipeline(content)?;

    let mut lineage_info = Vec::new();

    for (stage_name, stage) in pipeline.stages {
        let deps = stage.deps.unwrap_or_default();
        let outs = stage.outs.unwrap_or_default();

        if !deps.is_empty() && !outs.is_empty() {
            lineage_info.push((stage_name, deps, outs));
        }
    }

    Ok(lineage_info)
}

// NEW: Generate DVC metadata with dependencies
#[allow(dead_code)]
pub fn generate_dvc_file_with_dependencies(
    output_path: &str,
    output_hash: &str,
    dependencies: Vec<(String, Option<String>)>, // (path, hash)
    command: Option<String>,
) -> Result<String, AppError> {
    let outputs = vec![DvcOutput {
        md5: output_hash.to_string(),
        path: output_path.to_string(),
        cache: true,
    }];

    let deps: Vec<DvcDependency> = dependencies
        .into_iter()
        .map(|(path, hash)| DvcDependency {
            path,
            md5: hash,
            checksum: None,
        })
        .collect();

    let metadata = DvcMetadata {
        outs: outputs,
        deps: Some(deps),
        cmd: command,
        md5: Some(output_hash.to_string()),
    };

    let yaml_content = serde_yaml::to_string(&metadata)
        .map_err(|e| AppError::Internal(format!("Failed to serialize DVC file: {}", e)))?;

    Ok(yaml_content)
}
